home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
SPACE 2
/
SPACE - Library 2 - Volume 1.iso
/
magazi~1
/
278
/
dialogs.3
next >
Wrap
Text File
|
1986-11-01
|
21KB
|
476 lines
Permission to reprint or excerpt is granted only if the following line
appears at the top of the article:
ANTIC PUBLISHING INC., COPYRIGHT 1985. REPRINTED BY PERMISSION.
Professional GEM by Tim Oren
Column #3 - The Dialog Handler
A MEANINGFUL DIALOG
This issue of ST PRO GEM begins an exploration of ST GEM's dialog
handler. I will discuss basic system calls for presenting the dialog,
and then continue with techniques for initializing and reading on/off
button and "radio" button objects. We will also take some short
side-trips into the operation of the GEM Resource Construction Set to
assist you in building these dialogs.
There are a number of short C routines which accompany this column.
These are stored as file GEMCL3.XMO in DL 5 on SIG*ATARI. Before
reading this column, you should visit SIG*ATARI (go pcs-132) and
download this file.
DEFINING TERMS
A dialog box is an "interactive form" in which the user may enter
text and indicate selections by pointing with the mouse. Dialogs in
GEM are "modal", that is, when a dialog is activated other screen
functions such as menus and window controls are suspended until the
dialog is completed.
In most cases, the visual structure of a GEM dialog is specified
within your application's resource file. The GEM Resource
Construction Set (RCS) is used to build a picture of the dialog.
When the RCS writes out a resource, it converts that picture into a
tree of GEM drawing objects and stores this data structure within the
resource. Before your application can display the dialog, it must
load this resource file and find the address of the tree which defines
the dialog.
To load a resource, the AES checks its size and allocates memory
for the load. It then reads in the resource, adjusting internal
pointers to reflect the load address. Finally, the object sizes
stored in the resource are converted from characters to pixels using
the system font size.
(A note for those with Macintosh experience: Although Mac and GEM
resources share a name, there are fundamental differences which can be
misleading. A Mac resource is a fork within a file; a GEM resource is
a TOS file by itself. Mac resources may be paged in and out of
memory; GEM resources are monolithic. GEM resources are internally
tree structured; Mac resources are not. Finally, Mac resources
include font information, while ST GEM does this with font loading at
the VDI level.)
The resource load is done with the GEM AES call:
ok = rsrc_load(ADDR("MYAPP.RSC"));
"MYAPP" should be replaced with the name of your program.
Resources conventionally have the same primary name as their
application, with the RSC extent name instead of PRG. The ok flag
returned by rsrc_load will be FALSE is anything went wrong during the
load.
The most common causes of failure are the resource not being in the
application's subdirectory, or lack of sufficient memory for GEM to
allocate space for the resource. If this happens, you must terminate
the program immediately.
Once you have loaded the resource, you find the address of a
dialog's object tree with:
rsrc_gaddr(R_TREE,MYDIALOG,&tree);
Tree is a 32-bit variable which will receive the address of the root
node of the tree.
The mnemonic MYDIALOG should be replaced with the name you gave
your dialog when defining it in the RCS. At the same time that it
writes the resource, RCS generates a corresponding .H file containing
tree and object names. In order to use these mnemonics within your
program, you must include the name file in your compile: #include
"MYAPP.H"
BUG ALERT!
When using the DRI/Alcyon C compiler, .H files must be in the
compiler's home directory or they will not be found. This is
especially annoying using a two floppy drive ST development system.
The only way around this is to explicitly reference an alternate disk
in the #include, for instance: "B:MYAPP.H"
Now that the address of the dialog tree has been found, you are
ready to display it. The standard (and minimal) sequence for doing so
is given in routine hndl_dial() in the download. We will now walk
through each step in this procedure.
The form_center call establishes the location of the dialog on the
screen. Dialog trees generated by the RCS have an undefined origin
(upper-left corner).
Form_center computes the upper-left location necessary to center
the dialog on the screen, and inserts it into the OB_X and OB_Y fields
of the ROOT object of the tree. It also computes the screen rectangle
which the dialog will occupy on screen and writes its pixel
coordinates into variables xdial, ydial, wdial, and hdial.
There is one peculiarity of form_center which occasionally causes
trouble. Normally the rectangle returned in xdial, etc., is exactly
the same size as the basic dialog box.
However, when the OUTLINED enhancement has been specified for the
box, form_center adds a three pixel margin to the rectangle returned.
This causes the screen area under the outline to be correctly redrawn
later (see below). Note that OUTLINED is part of the standard dialog
box in the RCS. Other enhancements, such as SHADOWED or "outside"
borders are NOT handled in this fashion, and you must compensate for
them in your code.
The next part of the sequence is a form_dial call with a zero
parameter. This reserves the screen for the dialog action about to
occur. Note that the C binding given for form_dial in the DRI
documents is in error: there are nine parameters, not five. The first
set of xywh arguments is actually used with form_dial calls 1 and 2
only, but place holders must be supplied in all cases.
The succeeding form_dial call (parameter one) animates a "zoom box"
on the screen which moves and grows from the first screen rectangle
given to the second rectangle, where the dialog will be displayed.
The use of this call is entirely optional. In choosing whether to
use it or not, you should consider whether the origin of the "zoom" is
relevant to the operation. For instance, a zoom from the menu bar is
relatively meaningless, while a zoom from an object about to be edited
in the dialog provides visual feedback to the user, showing whether
the correct object was chosen.
If the origin is not relevant, then the zoom is just a time-waster.
If you decide to include these effects, consider a "preferences"
option in your app which will allow the experienced and jaded user to
turn them off in the interests of speed.
The objc_draw call actually displays the dialog on the screen.
Note that the address of the tree, the beginning drawing object, and
the drawing depth are passed as arguments, as well as the rectangle
allotted for the dialog.
In general, dialogs (and parts of dialogs) are ALWAYS drawn
beginning at the ROOT (object zero). When you want to draw only a
portion of the dialog, adjust the clipping rectangle, but not the
object number. This ensures that the background of the dialog is
always drawn correctly.
The objc_xywh() utility in the download can be used to find the
clipping rectangle for any object within a dialog, though you may have
to allow an extra margin is you have used shadows, outlines, or
outside borders with the object.
Calling form_do transfers control to the AES, which animates the
dialog for user interaction. The address of the dialog tree is passed
as a parameter. The second paramter is the number of the editable
object at which the text cursor will first be positioned. If you have
no text fields, pass a zero. Note that again the DRI documents are in
error: passing a -1 default may crash the system. Also be careful
that the default which you specify is actually a text field; no error
checking is performed.
The form_do call returns the number of the object on which the
clicked to terminate the dialog. Usually this is a button type object
with the EXIT and SELECTABLE attributes set. Setting the DEFAULT
attribute as well will cause an exit on that object is a carriage
return is struck while in the dialog.
If the top bit of the return is set, it indicates that the exit
object had the TOUCHEXIT attribute and was selected with a
double-click. Since very few dialogs use this combination, the sample
code simply masks off the top bit.
The next form_dial call reverses the "zoom box", moving it from the
dialog's location back to the given x,y,w,h. The same cautions apply
here as above.
The final form_dial call tells GEM that the dialog is complete, and
that the screen area occupied by the dialog is now considered "dirty"
and needs to be redrawn. Using the methods described in our last
column, GEM then sends redraws to all windows which were overlaid, and
does any necessary redrawing of the menu or desktop itself.
There is one notable "feature" of form_dial(3): It always redraws
an area which is two pixels wider and higher than your request! This
was probably included to make sure that drop-shadows were cleaned up,
and is usually innocuous.
A HANDY TRICK
Use of the form_dial(3) call is not limited to dialogs. You can
use it to force the system to redraw any part of the screen. The
advantage of this method is that the redraw area need not lie entirely
within a window, as was necessary with the send_redraw method detailed
in the last column. A disadvantage is that this method is somewhat
slower, since the AES has to decide who gets the redraws.
CLEAN UP
As a last step, you need to clear the SELECTED flag in the object
which was clicked. If you do not do this, the object will be drawn
inverted the next time you call the dialog. You could clear the flag
with the GEM objc_change call, but it is inefficient since you do not
need to redraw the object.
Instead, use the desel_obj() code in the download, which modifies
the object's OB_STATE field directly. Assuming that ret_obj contains
the exit object returned by hndl_dial, the call:
desel_obj(tree, ret_obj);
will do the trick.
RECAP
The basic dialog handling method I have described contains three
steps: initialization (rsrc_gaddr), dialog presentation (hndl_dial),
and cleanup (desel_obj).
As we build more advanced dialogs, these same basic steps will be
performed, but they will grow more complex. The initialization will
include setting up proper object text and states, and the cleanup
phase will also interrogate the final states of objects to find out
what the user did.
BUTTON, BUTTON
The simple dialogs described above contain only exit buttons as
active objects. As such, they are little more than glorified alert
boxes.
We will now increase the complexity a little by considering
non-exit buttons. These are constructed by setting the SELECTABLE
attribute on a button object.
At run-time, such an object will toggle its state between selected
(highlighted) and non-selected whenever the user clicks on it. (You
can set the SELECTABLE attribute of other types of objects and use
them instead of actual buttons, but be sure that the user will be able
to figure out what you intend!)
Having non-exit buttons forces us to consider the problem of
initializing them before the dialog, and interrogating and resetting
them afterward.
Since a button is a toggle, it is usually associated with a flag
variable in the program. As part of the initialization, you should
test the flag variable, and if true call:
sel_obj(tree, BTNOBJ);
which will cause the button to appear highlighted when the dialog is
first drawn. Sel_obj() is in the download. BTNOBJ is replaced with
the name you gave your button when you defined it in the RCS. Since
the button starts out deselected, you don't have to do anything if
your flag variable is false.
After the dialog has completed, you need to check the object's
state. The selectp() utility does so by masking the OB_STATE field.
You can simply assign the result of this test to your flag variable,
but be sure that the dialog was exited with an OK button, not with a
CANCEL! Again, remember to clean up the button with desel_obj().
(It's often easiest to deselect all buttons just before you leave the
dialog routine, regardless of the final dialog state.)
WHO'S GOT THE BUTTON?
Another common use of buttons in a dialog is to select one of a set
of possible options. In GEM, such objects are called radio buttons.
This term recalls automobile radio tuners where pushing in one button
pops out any others. In like fashion, selecting any one of a set of
radio buttons automatically deselects all of the others.
To use the radio button feature, you must do some careful work with
the Resource Construction Set.
First, each member of a set of radio buttons must be children of
the same parent object within the object tree. To create this
structure, put a hollow box type object in the dialog, make it big
enough to hold all of the buttons, and then put the buttons into the
box one at a time.
By nesting the buttons within the box object, you force them to be
its children. Each of the buttons must have both the SELECTABLE and
RADIO BUTTON attributes set. When you are done, you may make the
containing box invisible by setting its border to zero, but do not
FLATTEN it!
Since each radio button represents a different option, you must
usually assign a name to each object. When initializing the dialog,
you must check which option is currently set, and turn on the
corresponding button only. A chain of if-then-else structures assures
that only one button will be selected.
At the conclusion of the dialog, you must check each button with
selectp() and make the appropriate adjustments to internal variables.
Again, an if-then-else chain is appropriate since only one button may
be selected. Either deselect the chosen button within this chain or
do them all at the end.
There is one common use of radio buttons in which you may short-cut
this procedure. If the buttons each represent one possible value of a
numeric variable, for instance, a set of selector buttons representing
colors from zero to seven, then you can compute the initial object
directly.
In order for this technique to work, you must use a special
capability of the RCS. Insert the object corresponding to a zero
value at the top (or left) of your array of buttons, then put the
"one" button below (or right) of it, and so on.
When the buttons are complete, the SORT operation is used to
guarantee that the top/left object is in fact the first child of the
parent box with the others following in order. Due to the details of
object tree structure (to be discussed in the next column), this will
guarantee that these objects are contiguous in the resource.
If you assign a name (say BUTTON1) to the first button, then you
can initialize the correct button with the call:
sel_obj(tree, BUTTON1 + field);
where field is the variable of interest.
When the dialog is complete, you can scan the radio buttons to
compute the new value for the underlying variable. The encode()
procedure in the download will do this. As always, remember to
deselect the buttons at the end.
You can use offsets or multipliers if your variable's values don't
start with zero or increment by one. If the values are irregular you
may be able to use a lookup table, at the cost of additional code.
COMING UP NEXT
In the next column, I will discuss the internal structure of object
trees. Then we'll use that knowledge to build a piece of code which
will "walk" an entire tree and apply a function to each object. We'll
apply this code to do all of the button deselects with a single call!
I'll also look at handling editable text fields and discuss some ways
to alter a dialog's appearance at run-time.
DISPELL GREMLINS
An editing error caused an omission in the first installment of ST
PRO GEM. The window components RTARROW and DNARROW should have been
listed along with HSLIDE as the horizontal equivalents of the vertical
slider components which were discussed.
>>>>>>>>>>>>>>>>>>>>>>> Basic Dialog Handler <<<<<<<<<<<<<<<<<<<<<<<
WORD
hndl_dial(tree, def, x, y, w, h)
LONG tree;
WORD def;
WORD x, y, w, h;
{
WORD xdial, ydial, wdial, hdial, exitobj;
form_center(tree, &xdial, &ydial, &wdial, &hdial);
form_dial(0, x, y, w, h, xdial, ydial, wdial, hdial);
form_dial(1, x, y, w, h, xdial, ydial, wdial, hdial);
objc_draw(tree, ROOT, MAX_DEPTH, xdial, ydial, wdial, hdial);
exitobj = form_do(tree, def) & 0x7FFF;
form_dial(2, x, y, w, h, xdial, ydial, wdial, hdial);
form_dial(3, x, y, w, h, xdial, ydial, wdial, hdial);
return (exitobj);
}
>>>>>>>>>>>>>>>>>>>>>>> Object rectangle utility <<<<<<<<<<<<<<<<<<<<<<<<<
VOID
objc_xywh(tree, obj, p) /* get x,y,w,h for specified object */
LONG tree;
WORD obj;
GRECT *p;
{
objc_offset(tree, obj, &p->g_x, &p->g_y);
p->g_w = LWGET(OB_WIDTH(obj));
p->g_h = LWGET(OB_HEIGHT(obj));
}
>>>>>>>>>>>>>>>>>>>>>> Sample radio buttons after dialog <<<<<<<<<<<<<<<<<<<<
WORD
encode(tree, ob1st, num)
LONG tree;
WORD ob1st, num;
{
for (; num--; )
if (selectp(ob1st+num))
return(num);
return (-1);
}
>>>>>>>>>>>>>>>>>>>>>>> Object flag utilities <<<<<<<<<<<<<<<<<<<<<<<<<<<
VOID
undo_obj(tree, which, bit) /* clear specified bit in object state */
LONG tree;
WORD which, bit;
{
WORD state;
state = LWGET(OB_STATE(which));
LWSET(OB_STATE(which), state & ~bit);
}
VOID
desel_obj(tree, which) /* turn off selected bit of spcfd object*/
LONG tree;
WORD which;
{
undo_obj(tree, which, SELECTED);
}
VOID
do_obj(tree, which, bit) /* set specified bit in object state */
LONG tree;
WORD which, bit;
{
WORD state;
state = LWGET(OB_STATE(which));
LWSET(OB_STATE(which), state | bit);
}
VOID
sel_obj(tree, which) /* turn on selected bit of spcfd object */
LONG tree;
WORD which;
{
do_obj(tree, which, SELECTED);
}
BOOLEAN
statep(tree, which, bit)
LONG tree;
WORD which;
WORD bit;
{
return ( (LWGET(OB_STATE(which)) & bit) != 0);
}
BOOLEAN
selectp(tree, which)
LONG tree;
WORD which;
{
return statep(tree, which, SELECTED);
}